package org.boticelli.logsearch;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;
import org.apache.lucene.analysis.StopAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.boticelli.Bot;
import org.boticelli.controller.AbstractBotController;
import org.boticelli.controller.DecoratedLog;
import org.boticelli.dao.LogDAO;
import org.boticelli.model.LogEntry;
import org.boticelli.util.Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class SearchController extends AbstractBotController
{
    protected static Logger log = Logger.getLogger(SearchController.class);
    
    private static final int HITS_PER_PAGE = 20;

    private static final String SESSIONKEY_SEARCH_RESULTS = "boticelli.search-results";

    private LogIndexer logIndexer;

    private LogDAO logDAO;

    private QueryParser queryParser = new QueryParser("contents", new StopAnalyzer());

    @Autowired(required = true)
    public void setLogDAO(LogDAO logDAO)
    {
        this.logDAO = logDAO;
    }

    @Autowired(required = true)
    public void setLogIndexer(LogIndexer logIndexer)
    {
        this.logIndexer = logIndexer;
    }

    @RequestMapping("/search")
    public ModelAndView showSearch(HttpSession session, HttpServletRequest request,
            HttpServletResponse response, @RequestParam(value = "bot", required = false)
            String botId, @RequestParam(value = "search", required = true)
            String search, @RequestParam(value = "offset", required = false)
            Integer offset) throws CorruptIndexException, IOException
    {
        Bot bot = findBot(botId);
        ModelAndView mav = new ModelAndView("log");

        Hits hits;
        if (offset == null || offset < 0)
        {
            offset = Integer.valueOf(0);
            Query query = null;
            try
            {
                query = queryParser.parse(search);
            }
            catch (ParseException e)
            {
                log.info("", e);
                response.sendRedirect(request.getContextPath()+"/app/log/");
                return null;
            }
            int retries = 3;
            for (int i = 0; i < retries; i++)
            {
                if (logIndexer.flush())
                {
                    break;
                }
            }

            IndexSearcher searcher = new IndexSearcher(logIndexer.getReader());
            hits = searcher.search(query);
            session.setAttribute(SESSIONKEY_SEARCH_RESULTS, hits);

            response.sendRedirect(request.getContextPath() + "/app/search?search=" + search
                    + "&offset=" + offset);
            return null;
        }
        else
        {
            hits = (Hits) session.getAttribute(SESSIONKEY_SEARCH_RESULTS);
            if (hits == null)
            {
                response.sendError(404);
                return null;
            }
        }

        List<LogEntry> logs = new ArrayList<LogEntry>();
        String prevSearch = null;
        String nextSearch = null;
        String firstSearch = null;
        String lastSearch = null;
        int numberOfHits = hits.length();
        int last = Math.min(offset + HITS_PER_PAGE, hits.length());
        for (int i = offset; i < last; i++)
        {
            Document doc = hits.doc(i);
            Long id = Long.parseLong(doc.get("id"));
            logs.add(logDAO.read(id));
        }
        if (offset > 0)
        {
            prevSearch = searchURI(bot, search, offset - HITS_PER_PAGE);
            firstSearch = searchURI(bot, search, 0);
        }
        if (offset < hits.length() - HITS_PER_PAGE)
        {
            nextSearch = searchURI(bot, search, offset + HITS_PER_PAGE);
            lastSearch = searchURI(bot, search, numberOfHits - HITS_PER_PAGE);
        }

        List<DecoratedLog> decorated = DecoratedLog.decorate(logs);
        for (int i = 0; i < decorated.size(); i++)
        {
            decorated.get(i).setScore(hits.score(offset + i));
        }

        if (log.isTraceEnabled())
        {
            log.trace("Searching for " + search + " : " + logs);
        }
        mav.addObject("bot", findBot(botId));
        mav.addObject("logs", decorated);
        mav.addObject("maxpage", (numberOfHits + HITS_PER_PAGE - 1) / HITS_PER_PAGE);
        mav.addObject("offset", offset);
        mav.addObject("hpp", HITS_PER_PAGE);
        mav.addObject("hits", numberOfHits);
        mav.addObject("search", Util.unicodeHtmlEscape(search, true));
        mav.addObject("nextSearch", nextSearch);
        mav.addObject("prevSearch", prevSearch);
        mav.addObject("firstSearch", firstSearch);
        mav.addObject("lastSearch", lastSearch);
        mav.addObject("isSearch", true);
        return mav;
    }

    private String searchURI(Bot bot, String search, int offset)
    {
        if (offset < 0)
        {
            offset = 0;
        }
        StringBuilder sb = new StringBuilder();
        sb.append("/app/search?bot=").append(bot.getBeanName()).append("&search=").append(search)
                .append("&offset=").append(offset);

        return sb.toString();
    }
    
}
